﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using RestSharp.Portable;
using RestSharp.Portable.Authenticators;
using Newtonsoft.Json;
using System.Net.Http;
using System.IO;
using Trakt.Model;

namespace Trakt
{
    public class TraktAuthenticator : IAsyncAuthenticator
    {
        public const string AccessCodeServiceEndpoint = TraktClient.ApiBaseUrl + "/oauth/authorize";
        public const string AccessTokenServiceEndpoint = TraktClient.ApiBaseUrl + "/oauth/token";
        public const string LocalOAuthRedirectUrl = "urn:ietf:wg:oauth:2.0:oob";

        public ITraktClientConfiguration Configuration { get; private set; }
        public JsonSerializer Serializer { get; set; }
        public AuthenticationToken UserToken { get; private set; }


        public TraktAuthenticator(ITraktClientConfiguration configuration)
        {
            Configuration = configuration;
            Serializer = new JsonSerializer();
            Serializer.Converters.Add(new TraktResponseConverter());
        }

        public TraktAuthenticator(ITraktClientConfiguration configuration, JsonSerializer serializer)
        {
            Configuration = configuration;
            Serializer = serializer;
        }

        public Task Authenticate(IRestClient client, IRestRequest request)
        {
            return new Task(() => AuthenticateSynchronous(client, request));
        }

        private void AuthenticateSynchronous(IRestClient client, IRestRequest request)
        {
            if (UserToken != null && !UserToken.Expired)
                request.AddHeader("authorization", UserToken);
        }

        public async Task RefreshToken(string userCode)
        {
            // UserToken = UserToken == null ? await GetToken(userCode).ConfigureAwait(false) : await GetToken(userCode).ConfigureAwait(false);
            await GetToken(userCode);
        }

        private async Task<AuthenticationToken> GetToken(string userCode) // , bool refresh = false)
        {
            var tokenRequest = HttpWebRequest.CreateHttp(AccessTokenServiceEndpoint);
            tokenRequest.Method = HttpMethod.Post.Method;
            tokenRequest.ContentType = "application/json";
            tokenRequest.Headers["trakt-api-key"] = Configuration.ClientId;
            tokenRequest.Headers["trakt-api-version"] = "2";

            var json = new
            {
                code = userCode,
                client_id = Configuration.ClientId,
                client_secret = Configuration.ClientSecret,
                redirect_uri = Configuration.RedirectUri,
                grant_type = "authorization_code" // refresh ? "refresh_token" : "authorization_code"
            };

            using (var writer = new StreamWriter(await tokenRequest.GetRequestStreamAsync().ConfigureAwait(false)))
            {
                Serializer.Serialize(writer, json);
            }

            try
            {
                var response = await tokenRequest.GetResponseAsync().ConfigureAwait(false) as HttpWebResponse;

                using (var reader = new JsonTextReader(new StreamReader(response.GetResponseStream())))
                {
                    return Serializer.Deserialize<AuthenticationToken>(reader);
                }
            }
            catch (WebException e)
            {
                var reader = new StreamReader(e.Response.GetResponseStream());
                TraktTvException parsedEx = null;

                try
                {
                    parsedEx = new TraktTvException(Serializer.Deserialize<ErrorResponse>(new JsonTextReader(reader)), e);
                }
                catch
                {
                    throw new TraktTvException(
                        string.Format("Trakt.Tv returned a {0} error whoes body could not be parsed.",
                        ((HttpWebResponse)e.Response).StatusCode.ToString()),
                        e);
                }
                finally
                {
                    reader.Dispose();
                }

                throw parsedEx;
            }
        }

        /// <remarks> Format: http://api.v2.trakt.tv/oauth/authorize?response_type=code&client_id=client_id&redirect_uri=redirect_uri&state=state&username=username </remarks>
        public string GetUserAuthorizationCodeUrl(string redirectUri = LocalOAuthRedirectUrl, string username = null, string state = null)
        {
            // Using StringBuilder here is primitive, but fast
            var url = new StringBuilder(TraktClient.ApiBaseUrl);
            url.Append("/oauth/authorize?response_type=code&client_id=");
            url.Append(Configuration.ClientId);
            url.Append("&redirect_uri=");
            url.Append(redirectUri);

            if (!string.IsNullOrEmpty(state))
            {
                url.Append("&state=");
                url.Append(state);
            }

            if (!string.IsNullOrEmpty(username))
            {
                url.Append("&username=");
                url.Append(username);
            }

            return url.ToString();
        } 
    }
}
